package com.googlecode.restitory.api.servlet;

import java.io.BufferedReader;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.googlecode.restitory.api.ConflictException;
import com.googlecode.restitory.api.JsonHelper;
import com.googlecode.restitory.api.JsonService;
import com.googlecode.restitory.api.gae.DatastoreJsonService;

public class JsonServlet extends HttpServlet {

	private static final long serialVersionUID = -4139667332531565175L;

	private static final Logger LOG = LoggerFactory
			.getLogger(JsonServlet.class);

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			String query = req.getParameter("query");
			if (query != null) {
				query(req, resp);
				return;
			}
			String count = req.getParameter("count");
			if (count != null) {
				count(req, resp);
				return;
			}
			super.service(req, resp);
		} catch (ConflictException e) {
			LOG.warn("Conflict", e);
			resp.sendError(409);
		}
	}

	@Override
	protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		JsonService service = getJsonService();
		String path = req.getRequestURI();
		String rev = req.getParameter("_rev");
		if (rev != null) {
			service.delete(path, Long.parseLong(rev));
		} else {
			service.delete(path);
		}
		write(resp, path);
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		JsonService service = getJsonService();
		JsonObject json = service.get(req.getRequestURI());
		if (json == null) {
			resp.sendError(404);
			return;
		}
		write(resp, json);
	}

	private void query(HttpServletRequest req, HttpServletResponse resp) {
		String json = req.getParameter("query");
		JsonObject query;
		if ("true".equals(json)) {
			query = readBody(req);
		} else {
			query = (JsonObject) JsonHelper.parse(json);
		}
		query.addProperty("kind", req.getRequestURI());
		JsonService service = getJsonService();
		write(resp, service.query(query));
	}

	private void count(HttpServletRequest req, HttpServletResponse resp) {
		String json = req.getParameter("count");
		JsonObject query;
		if ("true".equals(json)) {
			query = readBody(req);
		} else {
			query = (JsonObject) JsonHelper.parse(json);
		}
		query.addProperty("kind", req.getRequestURI());
		JsonService service = getJsonService();
		write(resp, service.count(query));
	}

	private void write(HttpServletResponse resp, Object obj) {
		try {
			if (!(obj instanceof JsonElement)) {
				obj = JsonHelper.createBasic(obj);
			}
			if (obj instanceof JsonObject) {
				JsonObject json = (JsonObject) obj;
				Long lastModified = (Long) JsonHelper.jsonToBasicObject(json
						.get("_lastModified"));
				if (lastModified != null) {
					resp.setDateHeader("Last-Modified", lastModified);
				}
			}
			resp.setContentType("application/json");
			resp.setCharacterEncoding("UTF-8");
			resp.getWriter().write(obj.toString());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	protected void doPut(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		JsonObject json = readBody(req);
		JsonService service = getJsonService();
		String path = service.put(json);
		write(resp, path);
	}

	public static JsonService getJsonService() {
		return new DatastoreJsonService();
	}

	private JsonObject readBody(HttpServletRequest req) {
		return (JsonObject) readBody(req, JsonObject.class);
	}

	@SuppressWarnings("rawtypes")
	private JsonElement readBody(HttpServletRequest req, Class... validClasses) {
		try {
			checkContentType(req);
			BufferedReader reader = req.getReader();
			JsonElement json = JsonHelper.parse(reader);

			boolean valid = false;
			StringBuilder b = new StringBuilder();
			for (Class c : validClasses) {
				b.append("[");
				b.append(c.getSimpleName()).append(" ");
				b.append("]");
				if (c.equals(json.getClass())) {
					valid = true;
				}
			}
			if (!valid) {
				throw new RuntimeException(json.getClass().getSimpleName()
						+ " not supported for this method. Types supported ");
			}

			if (json.isJsonObject()) {
				JsonObject obj = (JsonObject) json;
				obj.addProperty("_self", req.getRequestURI());
			}
			return json;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private void checkContentType(HttpServletRequest req) {
		String contentType = ServletHelper.getMediaType(req.getContentType());
		if (contentType != null && !contentType.equals("application/json")) {
			throw new RuntimeException("Content-Type must be application/json");
		}
		String charset = req.getCharacterEncoding();
		if (charset == null) {
			throw new RuntimeException("charset is required in Content-Type");
		}
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		JsonElement json = readBody(req, JsonObject.class, JsonArray.class);
		JsonService service = getJsonService();

		if (json.isJsonObject()) {
			String path = service.post((JsonObject) json);
			write(resp, path);
		} else {
			JsonArray objs = (JsonArray) json;
			JsonArray result = new JsonArray();

			for (JsonElement jsonElement : objs) {
				JsonObject obj = (JsonObject) jsonElement;
				obj.addProperty("_self", req.getRequestURI());
				String path = service.post(obj);
				JsonPrimitive p = new JsonPrimitive(path);
				result.add(p);
			}
			write(resp, result);
		}
	}

}
